import asyncio
import aiohttp
import datetime
import json
from typing import Dict, Optional

# 配置参数
CONCURRENT_LIMIT = 50  # 并发请求数量限制，可根据服务器承受能力调整
START_CAMERA_ID = 10000  # 起始相机ID
END_CAMERA_ID = 30000  # 结束相机ID
REQUEST_TIMEOUT = 15  # 请求超时时间（秒）
SAVE_FILE_PATH = "./camera_ok.txt"  # 可用相机信息保存路径
TARGET_API_TEMPLATE = "http://h5.kezantech.com:7060/media-server/camera/play/info?cameraId={camera_id}&platform=android&t={timestamp}"

# 模拟浏览器请求头
HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    "Accept": "application/json, text/javascript, */*; q=0.01",
    "Content-Type": "application/json; charset=UTF-8"
}


async def check_camera_status_async(
        session: aiohttp.ClientSession,
        camera_id: int,
        semaphore: asyncio.Semaphore
) -> Dict:
    """
    异步检测相机接口状态
    :param session: 共享的aiohttp客户端会话
    :param camera_id: 相机ID
    :param semaphore: 信号量，用于控制并发数量
    :return: 检测结果字典
    """
    # 获取当前时间戳（毫秒级）
    timestamp = int(datetime.datetime.now().timestamp() * 1000)
    api_url = TARGET_API_TEMPLATE.format(camera_id=camera_id, timestamp=timestamp)

    try:
        # 使用信号量控制并发
        async with semaphore:
            async with session.get(
                    url=api_url,
                    headers=HEADERS,
                    timeout=REQUEST_TIMEOUT,
                    ssl=False  # 关闭SSL验证（生产环境需谨慎）
            ) as response:
                # 检查HTTP状态码
                if response.status != 200:
                    return {
                        "camera_id": camera_id,
                        "is_success": False,
                        "camera_available": None,
                        "camera_name": None,
                        "error_msg": f"HTTP请求失败，状态码：{response.status}"
                    }

                # 读取响应内容
                response_text = await response.text()

                # 解析JSON响应
                try:
                    response_json = json.loads(response_text)
                except json.JSONDecodeError as e:
                    return {
                        "camera_id": camera_id,
                        "is_success": False,
                        "camera_available": None,
                        "camera_name": None,
                        "error_msg": f"JSON解析失败：{str(e)}，原始响应：{response_text[:200]}..."
                    }

                # 处理业务逻辑
                interface_code = response_json.get("code")
                interface_msg = response_json.get("msg", "未知消息")

                if interface_code != 0:
                    return {
                        "camera_id": camera_id,
                        "is_success": False,
                        "camera_available": None,
                        "camera_name": None,
                        "error_msg": f"接口业务报错，code：{interface_code}，msg：{interface_msg}"
                    }

                # 提取相机信息
                data = response_json.get("data", {})
                play_able = data.get("playAble", False)
                camera_name = data.get("cameraName", "未知相机名称")
                media_url = data.get("mediaUrl", "")
                camera_available = play_able and bool(media_url)
                camera_status = "可用" if camera_available else "不可用"

                # 保存可用相机信息
                # if camera_available and "10017" in media_url:
                if camera_status == "可用":
                    content = f"{camera_id}, {camera_name}, {json.dumps(response_json, ensure_ascii=False)}"
                    print(f"✅ 发现可用相机：{content}")
                    with open(SAVE_FILE_PATH, "a", encoding="utf-8") as f:
                        f.write(content + "\n")

                return {
                    "camera_id": camera_id,
                    "is_success": True,
                    "camera_available": camera_available,
                    "camera_name": camera_name,
                    "error_msg": None,
                    "camera_status_desc": f"相机【{camera_name}】当前状态：{camera_status}"
                }

    # 捕获各类异步请求异常
    except aiohttp.ClientConnectionError:
        return {
            "camera_id": camera_id,
            "is_success": False,
            "camera_available": None,
            "camera_name": None,
            "error_msg": "网络连接错误（服务器未启动、IP/端口错误或网络不通）"
        }
    except asyncio.TimeoutError:
        return {
            "camera_id": camera_id,
            "is_success": False,
            "camera_available": None,
            "camera_name": None,
            "error_msg": "请求超时（服务器响应过慢）"
        }
    except aiohttp.InvalidURL:
        return {
            "camera_id": camera_id,
            "is_success": False,
            "camera_available": None,
            "camera_name": None,
            "error_msg": "URL格式无效（检查接口地址是否正确）"
        }
    except aiohttp.ClientSSLError:
        return {
            "camera_id": camera_id,
            "is_success": False,
            "camera_available": None,
            "camera_name": None,
            "error_msg": "SSL证书验证失败"
        }
    except Exception as e:
        return {
            "camera_id": camera_id,
            "is_success": False,
            "camera_available": None,
            "camera_name": None,
            "error_msg": f"未知错误：{str(e)}"
        }


async def batch_check_cameras() -> None:
    """批量异步检测相机状态主函数"""
    print(f"开始检测相机状态，范围：{START_CAMERA_ID} - {END_CAMERA_ID}，并发数：{CONCURRENT_LIMIT}")
    start_time = datetime.datetime.now()

    # 创建信号量控制并发
    semaphore = asyncio.Semaphore(CONCURRENT_LIMIT)

    # 创建aiohttp客户端会话（复用连接池，提高效率）
    timeout = aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        # 创建所有检测任务
        tasks = []
        for camera_id in range(START_CAMERA_ID, END_CAMERA_ID + 1):
            task = asyncio.create_task(
                check_camera_status_async(session, camera_id, semaphore)
            )
            tasks.append(task)

        # 批量处理任务结果
        for task in asyncio.as_completed(tasks):
            result = await task
            # 仅打印检测失败的信息（减少控制台输出）
            if not result["is_success"] or not result["camera_available"]:
                print(f"❌ 相机ID {result['camera_id']}：{result['error_msg']}")

    # 计算并显示总耗时
    end_time = datetime.datetime.now()
    total_time = (end_time - start_time).total_seconds()
    print(f"\n检测完成！总耗时：{total_time:.2f}秒，共检测{END_CAMERA_ID - START_CAMERA_ID + 1}个相机")


if __name__ == '__main__':
    # 解决Windows系统下的事件循环问题
    try:
        asyncio.run(batch_check_cameras())
    except RuntimeError as e:
        if "cannot be called from a running event loop" in str(e):
            # 用于Jupyter等已有事件循环的环境
            loop = asyncio.get_event_loop()
            loop.run_until_complete(batch_check_cameras())
        else:
            raise
    except KeyboardInterrupt:
        print("\n检测被用户手动中断")
    except Exception as e:
        print(f"程序运行出错：{str(e)}")
